package lispinterpreter;

import java.util.HashMap;

public class BlackBox {

    private String operators = "+ - * / < > = eq";

    private HashMap<String, LispList> mapDefinitions = new HashMap<String, LispList>();

    public LispList evaluate(LispList ins) {

	String op = ((LispAtom) ins.getElement(0)).satom;

	if (op.equals("setf")) {
	    ins = setf(ins);
	} else if (op.equals("if")) {
	    ins = cond(ins);
	} else if (op.equals("defun")) {
	    ins = defun(ins);
	} else if (operators.contains(op)) {
	    ins = tryReplaceVariables(ins);
	    ins = elemnetaryOperations(ins);
	} else if (!op.isEmpty())
	    ins = getDenifedValuesOf(op);
	else
	    System.err.println("Operando no reconocido.");

	return ins;
    }

    private LispList elemnetaryOperations(LispList ins) {
	// TODO Auto-generated method stub
	return ins;
    }

    private LispList defun(LispList ins) {
	// TODO Auto-generated method stub
	return null;
    }

    private LispList cond(LispList ins) {
	// TODO Auto-generated method stub
	return null;
    }

    /**
     * The setf special form uses its first argument to define a place in
     * memory, evaluates its second argument, and stores the resulting value in
     * the resulting memory location.
     */
    private LispList setf(LispList ins) {
	int indexOperands = 0;

	if (ins.size() == 3) // (setf x 10)
	    indexOperands = 2;
	else if (ins.size() == 4) // (setf x '(1 2 3))
	    indexOperands = 3;
	else { // Instrucción no válida
	    System.err.println("SETF called with an odd number of arguments.");
	}

	String variable = ((LispAtom) ins.getElement(1)).satom.toUpperCase();
	LispList operands = ins.getElement(indexOperands);

	mapDefinitions.put(variable, operands);
	return operands;
    }

    /**
     * Reemplaza las variables de la instrucción lisp.
     * 
     * @return lista LispList con las variables sustituidas.
     */
    public LispList tryReplaceVariables(LispList ins) {
	int len = ins.size();
	for (int i = 0; i < len; i++) {
	    LispList element = ins.getElement(i);

	    // Verifica si el elemento corresponde a un atomo tipo string,
	    // float o boolean. Si el tipo no corresponde, es porque se tiene
	    // una lista anidada la cual es enviada nuevamente a este método
	    // para su análisis.
	    if (element.sAtomVerifier() || element.fAtomVerifier()
		    || element.fAtomVerifier()) {

		LispAtom atom = (LispAtom) element;
		String expr = atom.satom;

		// Verifica si el operando corresponde a una funcion elementa.
		// Si no corresponde es por es una variable.
		if (expr != null && !operators.contains(expr)) {
		    LispList values = getDenifedValuesOf(expr.toUpperCase());
		    if (values == null) {
			System.err.print("Variable " + expr + " has no value.");
			break;
		    } else
			ins.replaceElement(i, values);
		}
	    } else { // element es lista anidada.
		tryReplaceVariables(element);
	    }
	}
	return ins;
    }

    /**
     * Obtiene la definición de la variable.
     * 
     * @param variableName nombre de la variable de la cual se deben recuperar
     *        su definción.
     * @return definición de la variable si la encuentra o null.
     */
    public LispList getDenifedValuesOf(String variableName) {
	LispList value = mapDefinitions.get(variableName);
	return value;
    }
}
